package com.alibaba.citrus.cr.common.lock;

import com.alibaba.citrus.cr.common.util.SpringContextUtil;
import com.alibaba.cz.base.tool.log.Log;
import com.alibaba.lattice2.epoch.middleware.cache.runtime.service.EpochCacheService;
import com.google.common.collect.Lists;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author luyi
 * @date 2021/11/29 7:19 下午
 */
public class RedisLock implements Lock{

    private static Log log = Log.getLogger(RedisLock.class);

    private static final EpochCacheService epochCacheService;

    static {
        epochCacheService = SpringContextUtil.getBean(EpochCacheService.class);
    }
    
    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_SLEEP_MAX_INTERNAL = 100;

    public RedisLock(String cacheCode,String lockKey) {
        this.cacheCode = cacheCode;
        this.lockKey = LOCK_PREFIX + lockKey;
    }

    public RedisLock(String cacheCode,String lockKey, long timeoutMsecs) {
        this(cacheCode,lockKey);
        this.timeoutMsecs = timeoutMsecs;
    }

    public RedisLock(String cacheCode,String lockKey, long timeoutMsecs, long expireMsecs) {
        this(cacheCode,lockKey, timeoutMsecs);
        this.expireMsecs = expireMsecs;
    }

    private String cacheCode;

    /**
     * Lock key path.
     */
    private String lockKey;

    /**
     * 默认1分钟
     * 锁超时时间，防止线程在入锁以后，无限的执行等待
     * 毫秒
     */
    private long expireMsecs = 60*1000;

    /**
     * 锁等待时间，防止线程饥饿
     * 毫秒
     */
    private long timeoutMsecs = 1000;

    private boolean locked = false;

    private String value;

    @Override
    public boolean lock() {
        long waitMillis = timeoutMsecs;
        value = UUID.randomUUID().toString();
        while (waitMillis >= 0) {
            long startNanoTime = System.nanoTime();
            locked = epochCacheService.setnx(cacheCode, lockKey, value);
            if (locked){
                epochCacheService.expire(cacheCode, lockKey,expireMsecs,TimeUnit.MILLISECONDS);
                return locked;
            }
            if (waitMillis == 0){
                return false;
            }
            int sleepMillis = new Random().nextInt((int) Math.min(waitMillis, DEFAULT_SLEEP_MAX_INTERNAL));
            sleep(sleepMillis);
            long escapedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanoTime);
            waitMillis = waitMillis - escapedMillis;
        }
        return false;
    }

    private void sleep(int sleepMillis) {
        try {
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            throw new RuntimeException(" Redis " + cacheCode + "-" + lockKey + " InterruptedException", e);
        }
    }

    @Override
    public void unlock() {
        if (!locked) {
            return;
        }
        String s = epochCacheService.get(cacheCode, lockKey);
        if (s == null || !s.equals(value)){
            return;
        }
        boolean remove = epochCacheService.remove(cacheCode, Lists.newArrayList(lockKey));
        if (remove){
            locked = false;
            log.info("ThreadId:"+Thread.currentThread().getId()+"Redis.key="+lockKey+" unlock");
        }
    }

    @Override
    public String getLockKey() {
        return lockKey;
    }

    @Override
    public void close() {
        unlock();
    }
}
